package com.keqiang.chart.impl.pie.impl.pseudo3d

import android.graphics.Canvas
import android.graphics.Paint
import android.text.Layout
import android.text.TextPaint
import com.keqiang.chart.impl.pie.LabelGravity
import com.keqiang.chart.impl.pie.LabelPosition
import com.keqiang.chart.impl.pie.impl.normal.PieEntity
import com.keqiang.chart.impl.pie.render.BaseLabelRender
import com.keqiang.chart.utils.ObjectPool
import com.keqiang.chart.utils.px
import com.keqiang.chart.utils.to360Range
import com.keqiang.chart.utils.toRadian
import com.keqiang.utils.PaintUtils
import com.keqiang.utils.PaintUtils.getChartHeight
import kotlin.math.*

/**
 * 饼状图标签绘制器
 *
 * @author Created by wanggaowan on 2022/8/8 14:36
 */
open class PieChart3DLabelRender(chart: PieChart3D) : BaseLabelRender<PieChart3D>(chart) {

    override fun onDraw(canvas: Canvas) {
        val dataList = chart.data
        if (dataList.isNullOrEmpty()) {
            return
        }

        val labelPosition = chart.labelPosition
        if (labelPosition is LabelPosition.Inside) {
            drawInSideLabel(chart, canvas, labelPosition, dataList)
            return
        }

        drawOutsideLabel(chart, canvas, labelPosition as LabelPosition.OutSide, dataList)
    }

    /**
     * 绘制内容位置在饼状图内部的标签
     */
    protected open fun drawInSideLabel(chart: PieChart3D, canvas: Canvas, labelPosition: LabelPosition.Inside, dataList: List<PieEntity>) {
        val (centerX, centerY) = chart.getCenter()
        val textPaint = ObjectPool.obtainPaint<TextPaint>(true)
        val path = ObjectPool.obtainPath()
        val pathMeasure = ObjectPool.obtainPathMeasure()
        val floatArray = ObjectPool.obtainFloatArray()

        dataList.forEach {
            if (it.isDraw && it.drawLabel) {
                val radius2 =
                    (it.drawRadius + sqrt(it.offsetDx.pow(2) + it.offsetDy.pow(2))) * labelPosition.offsetCenterPercent
                val radiusShort = radius2 * cos(chart.getDrawFlipAngle().toRadian)

                val startAngle: Float
                val sweepAngle: Float
                if (it.drawSweepAngle == 0f) {
                    // 使用PathMeasure测量路径坐标点时，如果path长度为0，则坐标将为0，0点
                    // 因此将开始角度往后偏移10°，让结束点在开始位置
                    startAngle = it.drawStartAngle - 10f
                    sweepAngle = 10f
                } else {
                    startAngle = it.drawStartAngle
                    sweepAngle = it.drawSweepAngle / 2f
                }

                // 此时圆心为圆柱顶部椭圆圆心
                path.reset()
                path.arcTo(centerX - radius2, centerY - radiusShort,
                    centerX + radius2, centerY + radiusShort, startAngle, sweepAngle, false)
                pathMeasure.setPath(path, false)
                pathMeasure.getPosTan(pathMeasure.length, floatArray, null)

                val x1 = floatArray[0]
                val y1 = floatArray[1]

                it.label?.let { label ->
                    textPaint.color = it.labelColor
                    textPaint.textSize = it.labelSize
                    val width = textPaint.measureText(label)
                    canvas.drawText(label, x1 - width / 2f, y1, textPaint)
                }

                it.secondLabel?.let { label ->
                    textPaint.color = it.secondLabelColor
                    textPaint.textSize = it.secondLabelSize
                    val width = textPaint.measureText(label)
                    canvas.drawText(label, x1 - width / 2f, y1 + textPaint.getChartHeight(), textPaint)
                }
            }
        }

        ObjectPool.recyclePaint(textPaint)
        ObjectPool.recyclePath(path)
        ObjectPool.recyclePathMeasure(pathMeasure)
        ObjectPool.recycleFloatArray(floatArray)
    }

    /**
     * 绘制内容位置在饼状图外部的标签
     */
    protected open fun drawOutsideLabel(chart: PieChart3D, canvas: Canvas, labelPosition: LabelPosition.OutSide, dataList: List<PieEntity>) {
        val (centerX, centerY) = chart.getCenter()
        val tempPath = ObjectPool.obtainPath()
        val tempRectF = ObjectPool.obtainRectF()
        val tempPathMeasure = ObjectPool.obtainPathMeasure()
        val tempFloatArray = ObjectPool.obtainFloatArray()
        val paint = ObjectPool.obtainPaint<Paint>()
        val textPaint = ObjectPool.obtainPaint<TextPaint>(true)

        dataList.forEach {
            if (it.isDraw && it.drawLabel) {
                val indicatorLine = labelPosition.lineFactory.invoke(it)
                val centerAngle = (it.drawStartAngle + it.drawSweepAngle / 2f).to360Range
                val centerRadian = centerAngle.toRadian
                val flipAngle = chart.getDrawFlipAngle()
                val height = chart.getDrawHeight()
                var radius2 =
                    it.drawRadius + sqrt(it.offsetDx.pow(2) + it.offsetDy.pow(2)) + indicatorLine.offset
                var radiusShort = radius2 * cos(flipAngle.toRadian)

                val startAngle: Float
                val sweepAngle: Float
                if (it.drawSweepAngle == 0f) {
                    // 使用PathMeasure测量路径坐标点时，如果path长度为0，则坐标将为0，0点
                    // 因此将开始角度往后偏移10°，让结束点在原始的开始位置
                    startAngle = it.drawStartAngle - 10f
                    sweepAngle = 10f
                } else {
                    startAngle = it.drawStartAngle
                    sweepAngle = it.drawSweepAngle / 2f
                }

                if (indicatorLine.drawStartCircle && indicatorLine.circleRadius > 0) {
                    // 绘制指示线起始端处的圆点
                    radius2 += indicatorLine.circleRadius
                    radiusShort = radius2 * cos(flipAngle.toRadian)
                    tempPath.reset()
                    if (centerAngle > 180 && centerAngle < 360) {
                        // 此时圆心为圆柱顶部椭圆圆心
                        tempPath.arcTo(centerX - radius2, centerY - radiusShort,
                            centerX + radius2, centerY + radiusShort, startAngle, sweepAngle, false)
                    } else {
                        // 此时圆心为圆柱底部椭圆圆心
                        tempPath.arcTo(centerX - radius2, centerY - radiusShort + height,
                            centerX + radius2, centerY + radiusShort + height, startAngle, sweepAngle, false)
                    }

                    tempPathMeasure.setPath(tempPath, false)

                    tempPathMeasure.getPosTan(tempPathMeasure.length, tempFloatArray, null)
                    paint.style = Paint.Style.FILL
                    paint.color = indicatorLine.circleColor
                    canvas.drawCircle(tempFloatArray[0], tempFloatArray[1], indicatorLine.circleRadius, paint)
                    radius2 += indicatorLine.circleRadius
                    radiusShort = radius2 * cos(flipAngle.toRadian)
                }

                tempPath.reset()
                if (centerAngle > 180 && centerAngle < 360) {
                    // 此时圆心为圆柱顶部椭圆圆心
                    tempPath.arcTo(centerX - radius2, centerY - radiusShort,
                        centerX + radius2, centerY + radiusShort, startAngle, sweepAngle, false)
                } else {
                    // 此时圆心为圆柱底部椭圆圆心
                    tempPath.arcTo(centerX - radius2, centerY - radiusShort + height,
                        centerX + radius2, centerY + radiusShort + height, startAngle, sweepAngle, false)
                }
                tempPathMeasure.setPath(tempPath, false)

                tempPathMeasure.getPosTan(tempPathMeasure.length, tempFloatArray, null)
                // 指示线第一段起始X坐标
                val x1: Float = tempFloatArray[0]
                // 指示线第一段起始Y坐标
                val y1: Float = tempFloatArray[1]

                // 标签宽度
                val labelWidth: Float
                val labelHeight = if (it.label.isNullOrEmpty()) {
                    labelWidth = 0f
                    0f
                } else {
                    textPaint.textSize = it.labelSize
                    labelWidth = textPaint.measureText(it.label)
                    textPaint.getChartHeight(includePadding = false)
                }

                // 第二个标签宽度
                val secondLabelWidth: Float
                val secondLabelHeight =
                    if (it.secondLabel.isNullOrEmpty() || labelPosition.labelGravity == LabelGravity.END) {
                        secondLabelWidth = 0f
                        0f
                    } else {
                        textPaint.textSize = it.secondLabelSize
                        secondLabelWidth = textPaint.measureText(it.secondLabel)
                        textPaint.getChartHeight(includePadding = false)
                    }

                // 两个标签中的最大值
                var maxWidth = labelWidth.coerceAtLeast(secondLabelWidth)
                if (maxWidth < indicatorLine.minLength) {
                    maxWidth = indicatorLine.minLength
                } else if (indicatorLine.maxLength != null && maxWidth > indicatorLine.maxLength!!) {
                    maxWidth = indicatorLine.maxLength!!
                }

                // 为了确保文本不会与饼状图重叠，计算在饼状图半径基础上的补充半径
                val addedRadius = if (centerAngle in 0.0..180.0) {
                    when (labelPosition.labelGravity) {
                        LabelGravity.TOP -> labelHeight
                        LabelGravity.END -> labelHeight / 2f
                        else -> secondLabelHeight
                    }
                } else {
                    when (labelPosition.labelGravity) {
                        LabelGravity.TOP -> secondLabelHeight
                        LabelGravity.END -> labelHeight / 2f
                        else -> labelHeight
                    }
                }

                // 标签水平方向与指示线间距
                val labelHorizontalOffset = 10f.px
                // 指示线第一段最小长度为10 px
                var minOneLine = addedRadius * abs(sin(centerRadian))
                if (minOneLine < 10f.px) {
                    minOneLine = 10f.px
                }
                val x2 = x1 + minOneLine * cos(centerRadian)
                val y2 = y1 + minOneLine * sin(centerRadian)

                val x3 = x2 + if (x2 >= centerX)
                    if (labelPosition.labelGravity == LabelGravity.END) {
                        // 10px为绘制在指示线尾部时，第二根线条默认长度
                        10f.px
                    } else {
                        labelHorizontalOffset + maxWidth
                    }
                else if (labelPosition.labelGravity == LabelGravity.END) {
                    (-10f).px
                } else {
                    -labelHorizontalOffset - maxWidth
                }

                tempPath.reset()
                tempPath.moveTo(x1, y1)
                tempPath.lineTo(x2, y2)
                tempPath.lineTo(x3, y2)

                paint.style = Paint.Style.STROKE
                paint.strokeWidth = indicatorLine.width
                paint.color = indicatorLine.color
                canvas.drawPath(tempPath, paint)

                it.label?.let { label ->
                    when (labelPosition.labelGravity) {
                        LabelGravity.TOP -> {
                            if (x3 - x2 > 0) {
                                tempRectF.set(x2 + labelHorizontalOffset, y2 - indicatorLine.width - labelHeight, x3, y2)
                            } else {
                                tempRectF.set(x3, y2 - indicatorLine.width - labelHeight, x2 - labelHorizontalOffset, y2)
                            }
                        }

                        LabelGravity.END -> {
                            if (x3 - x2 > 0) {
                                tempRectF.set(x3 + labelHorizontalOffset, y2 - labelHeight / 2f,
                                    x3 + maxWidth + labelHorizontalOffset, y2 + labelHeight / 2f)
                            } else {
                                tempRectF.set(x3 - maxWidth - labelHorizontalOffset, y2 - labelHeight / 2f,
                                    x3 - labelHorizontalOffset, y2 + labelHeight / 2f)
                            }
                        }

                        else -> {
                            if (x3 - x2 > 0) {
                                tempRectF.set(x2 + labelHorizontalOffset, y2, x3, y2 + indicatorLine.width + labelHeight)
                            } else {
                                tempRectF.set(x3, y2, x2 - labelHorizontalOffset, y2 + indicatorLine.width + labelHeight)
                            }
                        }
                    }

                    canvas.save()
                    canvas.translate(tempRectF.left, tempRectF.top)
                    val align = if (labelPosition.labelGravity == LabelGravity.END) {
                        if (x3 - x2 > 0) Layout.Alignment.ALIGN_NORMAL else Layout.Alignment.ALIGN_OPPOSITE
                    } else if (x3 - x2 > 0) Layout.Alignment.ALIGN_OPPOSITE else Layout.Alignment.ALIGN_NORMAL
                    textPaint.color = it.labelColor
                    textPaint.textSize = it.labelSize
                    PaintUtils.drawText(canvas, textPaint, label, maxWidth.toInt(), 1, align, true)
                    canvas.restore()
                }

                if (secondLabelHeight > 0) {
                    it.secondLabel?.let { label ->
                        when (labelPosition.labelGravity) {
                            LabelGravity.TOP -> {
                                if (x3 - x2 > 0) {
                                    tempRectF.set(x2 + labelHorizontalOffset, y2, x3, y2 + indicatorLine.width + secondLabelHeight)
                                } else {
                                    tempRectF.set(x3, y2, x2 - labelHorizontalOffset, y2 + indicatorLine.width + secondLabelHeight)
                                }
                            }

                            else -> {
                                if (x3 - x2 > 0) {
                                    tempRectF.set(x2 + labelHorizontalOffset, y2 - indicatorLine.width - secondLabelHeight, x3, y2)
                                } else {
                                    tempRectF.set(x3, y2 - indicatorLine.width - secondLabelHeight, x2 - labelHorizontalOffset, y2)
                                }
                            }
                        }

                        canvas.save()
                        canvas.translate(tempRectF.left, tempRectF.top)
                        val align =
                            if (x3 - x2 > 0) Layout.Alignment.ALIGN_OPPOSITE else Layout.Alignment.ALIGN_NORMAL
                        textPaint.color = it.secondLabelColor
                        textPaint.textSize = it.secondLabelSize
                        PaintUtils.drawText(canvas, textPaint, label, maxWidth.toInt(), 1, align, true)
                        canvas.restore()
                    }
                }
            }
        }

        ObjectPool.recyclePath(tempPath)
        ObjectPool.recycleRectF(tempRectF)
        ObjectPool.recyclePathMeasure(tempPathMeasure)
        ObjectPool.recycleFloatArray(tempFloatArray)
        ObjectPool.recyclePaint(paint)
        ObjectPool.recyclePaint(textPaint)
    }
}